"""Plugin to graph ADC data from sensors.

Based on Gordon Williams' demo from wx.lib.plot

See also L{Dingo.pluginManager.Plugin}
"""


from __future__ import absolute_import
del absolute_import
from ... import pluginManager

import copy, Queue, socket, time, thread, threading, wx

from wx.lib.newevent import *
from wx.lib.plot import *

# Needs Numeric or numarray or NumPy
try:
    import numpy.oldnumeric as _Numeric
except:
    try:
        import numarray as _Numeric
    except:
        try:
            import Numeric as _Numeric
        except:
            msg= """
            This module requires the Numeric/numarray or NumPy module,
            which could not be imported.  It probably is not installed
            (it's not part of the standard Python distribution). See the
            Numeric Python site (http://numpy.scipy.org) for information on
            downloading source or binaries."""
            raise ImportError, "Numeric,numarray or NumPy not found. \n" + msg

__author__ = 'Sarah Mount <s.mount@wlv.ac.uk>'
__date__ = 'April 2008'
__credits__ = 'Gordon Williams -- wx.lib.plot demo'

port = 11000
host = socket.gethostname()
buffsize = 1024

adcevents = Queue.Queue() 
"""var: Thread-safe queue to hold ADC events.
Message format:
 <str>,<str>,<float>,<float>
 E.g. Sensor100,Humidity,213.44,45
      Sensor,Transducer,Reading,Timestamp
"""

ADCEvent, EVT_ADC = wx.lib.newevent.NewEvent()

markers = ['circle', 'dot', 'square', 'triangle', 'triangle_down', 'cross', 'plus']
colours = ['red', 'green', 'blue', 'yellow', 'white']

class ADCReadingsPlugin(pluginManager.Plugin):
    """
    Plot values read on real or simulated ADCs.
    
    Listens on port 11000 of localhost.
    """
    def __init__(self, name):
        """
        Note that we can't interact with the GUI here because the plugin
        notebook page will only be created by the Plugin Manager after
        the plugin is activated.
        """
	pluginManager.Plugin.__init__(self, name)
        self.socketlistener = None
        adcevents.put('SensorNull,0,0') # Needs to be initialised for plot canvas
        return

    def startSocketListener(self):
        self.socketlistener = thread.start_new_thread(socketlistener, (self.panel,))
        return

    def stopSocketListener(self):
	sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.sendto('LISTENER_EXIT', (host, port))
        return

    def activate(self):
        self.plotter = GenericPlotter(self.panel, wx.ID_ANY)
        self.plotter.client.SetSize(self.panel.GetClientSize())
        self.panel.Bind(wx.EVT_SIZE, self.OnResize)
	self.plotter.client.Bind(EVT_ADC, self.plotter.OnADCEvent)
	EVT_ADC(self.plotter.client, ADCEvent)
        self.startSocketListener()
        print 'Plugin ' + self.name + ' activated.'
        return

    def deactivate(self):
        self.stopSocketListener()	
        print 'Plugin ' + self.name + ' deactivated.'
        return

    def createMenu(self):
        return self.plotter.createMenu()

    def OnResize(self, event):
        self.plotter.client.SetSize(self.plotter.client.GetParent().GetSize())
        return
 

def socketlistener(wxapp):
	"""Listen on a UDP socket for debug data."""
	sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	sock.bind((host, port))
	while True:
    		msg, addr = sock.recvfrom(buffsize)
                if msg == 'LISTENER_EXIT':
                    return                
                messages.put(msg)
                wx.PostEvent(wxapp, ADCEvent())
    	return

def createEmptyData():
    points=[]
    line1 = PolyLine(points, legend='', colour='white', width=1)
    return PlotGraphics([line1], 'ADC Readings', 'Time', 'Units')

def createData():
	"""Return an object to place on a plotting canvas."""
	markers = []
        while not adcevents.empty():
            msg = adcevents.get().split(',')
            if not len(msg) == 4:
                print 'ADC Reader got bad data:', msg
                continue
            markers.append(PolyLine((msg[2], msg[3]), legend=(msg[0]+':'+msg[1])))
    	return PlotGraphics(markers, 'ADC Reading', 'Time', 'Units')

            
#         if PlottingApp.unmarshaller.isLine:
#             for key in data:
# 		markers.append(PolyLine(data[key][-PlottingApp.unmarshaller.max_data:],
#                                    	legend=PlottingApp.unmarshaller.config[key].legend,
# 	        	           	colour=PlottingApp.unmarshaller.config[key].colour,
#                                    	width=PlottingApp.unmarshaller.config[key].width))
#         else:
#             for key in data:
#     		markers.append(PolyMarker(data[key][-PlottingApp.unmarshaller.max_data:],
#         				  legend=PlottingApp.unmarshaller.config[key].legend,
#         	        	          colour=PlottingApp.unmarshaller.config[key].colour,
#                                 	  width=PlottingApp.unmarshaller.config[key].width,
#                                 	  marker=PlottingApp.unmarshaller.config[key].marker))



class GenericPlotter(wx.Panel):
    def __init__(self, parent, id):
        self._create_gui(parent)

	self.OnPlotDraw(True)
	self.client.Bind(EVT_ADC, self.OnADCEvent)
	EVT_ADC(self.client, self.OnADCEvent)
	return

    def _create_gui(self, parent):
        self.client = PlotCanvas(parent, style= wx.BORDER_SUNKEN)
        self.client.InheritAttributes()
        # Define the function for drawing pointLabels
        self.client.SetPointLabelFunc(self.DrawPointLabel)
        # Create mouse event for showing cursor coords in status bar
        self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
        # Show closest point when enabled
        self.client.Bind(wx.EVT_MOTION, self.OnMotion)
        return

    def createMenu(self):
        menu = wx.Menu()

        item = wx.MenuItem(menu, wx.ID_ANY, '&Redraw', 'Redraw plots')
        self.client.Bind(wx.EVT_MENU, self.OnPlotRedraw, item)
        menu.AppendItem(item)
        
        item = wx.MenuItem(menu, wx.ID_ANY, '&Delete plot', 'Delete canvas')
        self.client.Bind(wx.EVT_MENU,self.OnPlotClear, item)
        menu.AppendItem(item)
        
        item = wx.MenuItem(menu, wx.ID_ANY, '&Scale', 'Scale canvas')
        self.client.Bind(wx.EVT_MENU,self.OnPlotScale, item)
        menu.AppendItem(item)
        
        item = wx.MenuItem(menu, wx.ID_ANY, 'Enable &Zoom', 'Enable Mouse Zoom',
                    kind=wx.ITEM_CHECK)
        self.client.Bind(wx.EVT_MENU,self.OnEnableZoom, item)
        menu.AppendItem(item)
        
        item = wx.MenuItem(menu, wx.ID_ANY, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK)
        self.client.Bind(wx.EVT_MENU,self.OnEnableGrid, item)
        menu.AppendItem(item)
        
        item = wx.MenuItem(menu, wx.ID_ANY, 'Enable &Legend', 'Turn on Legend',
                    kind=wx.ITEM_CHECK)
        self.client.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220)
        menu.AppendItem(item)
        
        item = wx.MenuItem(menu, wx.ID_ANY, 'Enable &Point Label', 'Show Closest Point',
                    kind=wx.ITEM_CHECK)
        self.client.Bind(wx.EVT_MENU,self.OnEnablePointLabel, item)
        menu.AppendItem(item)
        
        item = wx.MenuItem(menu, wx.ID_ANY,  '&Reset zoom', 'Reset zoom')
        self.client.Bind(wx.EVT_MENU,self.OnReset, item)
        menu.AppendItem(item)

	return menu


    def OnADCEvent(self, evt):
        """Called from the thread listening for ADC events. 
        Passes PlotGraphics object to be drawn on the plot canvas.
        """
        self.client.Draw(evt.points)
        return
    
    def DrawPointLabel(self, dc, mDataDict):
        """This is the fuction that defines how the pointLabels are plotted
            dc - DC that will be passed
            mDataDict - Dictionary of data that you want to use for the pointLabel

            As an example I have decided I want a box at the curve point
            with some text information about the curve plotted below.
            Any wxDC method can be used.
        """
        # ----------
        dc.SetPen(wx.Pen(wx.WHITE))
        dc.SetBrush(wx.Brush( wx.WHITE, wx.SOLID ) )        
        sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
        dc.DrawRectangle( sx-5,sy-5, 10, 10)  #10by10 square centered on point
        px,py = mDataDict["pointXY"]
        cNum = mDataDict["curveNum"]
        pntIn = mDataDict["pIndex"]
        legend = mDataDict["legend"]
        #make a string to display
        s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
        dc.DrawText(s, sx , sy+1)
        # -----------

    def OnMouseLeftDown(self,event):
        s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client._getXY(event)
        self.SetStatusText(s)
        event.Skip()            #allows plotCanvas OnMouseLeftDown to be called

    def OnMotion(self, event):
        #show closest point (when enbled)
        if self.client.GetEnablePointLabel() == True:
            #make up dict with info for the pointLabel
            #I've decided to mark the closest point on the closest curve
            dlst= self.client.GetClosestPoint( self.client._getXY(event), pointScaled= True)
            if dlst != []:    #returns [] if none
                curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
                #make up dictionary to pass to my user function (see DrawPointLabel) 
                mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
                            "pointXY":pointXY, "scaledXY":scaledXY}
                #pass dict to update the pointLabel
                self.client.UpdatePointLabel(mDataDict)
        event.Skip()           #go to next handler

    def OnFilePageSetup(self, event):
        self.client.PageSetup()
        
    def OnFilePrintPreview(self, event):
        self.client.PrintPreview()
        
    def OnFilePrint(self, event):
        self.client.Printout()
        
    def OnSaveFile(self, event):
        self.client.SaveFile()

#    def OnFileExit(self, event):
#        self.Close()

    def OnPlotDraw(self, event):
        """Only called on startup now.
        OnADCEvent events are posted by the thread listening on the socket.
        """
        self.resetDefaults()
#        self.client.SetFont(wx.Font(10,wx.NORMAL,wx.NORMAL,wx.NORMAL))
#        self.client.SetFontSizeAxis(10)
#        self.client.SetFontSizeLegend(7)
        self.client.SetXSpec('min')
        self.client.SetYSpec('min')
        self.client.Draw(createEmptyData())

    def OnPlotRedraw(self,event):
        self.client.Redraw()

    def OnPlotClear(self,event):
        self.client.Clear()
        
    def OnPlotScale(self, event):
        if self.client.last_draw != None:
            graphics, xAxis, yAxis= self.client.last_draw
            self.client.Draw(graphics,(1,3.05),(0,1))

    def OnEnableZoom(self, event):
        self.client.SetEnableZoom(event.IsChecked())
#        self.mainmenu.Check(217, not event.IsChecked())
        
    def OnEnableGrid(self, event):
        self.client.SetEnableGrid(event.IsChecked())
        
    def OnEnableDrag(self, event):
        self.client.SetEnableDrag(event.IsChecked())
#        self.mainmenu.Check(214, not event.IsChecked())
        
    def OnEnableLegend(self, event):
        self.client.SetEnableLegend(event.IsChecked())

    def OnEnablePointLabel(self, event):
        self.client.SetEnablePointLabel(event.IsChecked())

    def OnScrUp(self, event):
        self.client.ScrollUp(1)
        
    def OnScrRt(self,event):
        self.client.ScrollRight(2)

    def OnReset(self,event):
        self.client.Reset()

    def resetDefaults(self):
        """Just to reset the fonts back to the PlotCanvas defaults"""
	# BROKEN!
        self.client.SetBackgroundColour(wx.NamedColour('black'))
	self.client.SetForegroundColour(wx.NamedColour('white'))
        self.client.SetOwnBackgroundColour(wx.NamedColour('black'))
	self.client.SetOwnForegroundColour(wx.NamedColour('white'))

        self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
        self.client.SetFontSizeAxis(10)
        self.client.SetFontSizeLegend(7)
        self.client.setLogScale((False,False))
        self.client.SetEnableGrid(True)
        self.client.SetEnableZoom(False)
        self.client.SetEnableDrag(False)        
        self.client.SetXSpec('auto')
        self.client.SetYSpec('auto')
        return


adcPlugin = ADCReadingsPlugin('ADC Readings')
